//: Playground - noun: a place where people can play

import UIKit

//:   泛型: 使用泛型能更清晰更简洁的表达代码意图
// 泛型所解决的问题
func swapTwoInts(a: inout Int, _ b: inout Int) { // 交换两个Int的值
    let temp = a
    a = b
    b = temp
}
var someInt = 3
var anotherInt = 5
swapTwoInts(a: &someInt, &anotherInt)
print("someInt:\(someInt),anotherInt:\(anotherInt)")
// 但如果想交换String Double类型的数该怎样做呢?再写两个函数?


//:  泛型函数: 在函数名后面添加< T >,表示T是函数定义的占位类型,只要前后一致,别的字母也可以
func swapTwoValues<T>(a: inout T, _ b: inout T) { // 用T来替代实际类型,a和b是同一类型T
    let temp = a
    a = b
    b = temp
}
swapTwoValues(a: &someInt, &anotherInt) // 在调用函数时,根据实际类型推断出T的类型
print("someInt:\(someInt),anotherInt:\(anotherInt)")

var StringA = "A"
var StringB = "B"
swapTwoValues(a: &StringA, &StringB)    // 只要传入任何相同的类型都可以
print("StringA:\(StringA),StringB:\(StringB)") // StringA:B,StringB:A


//:  类型参数
// 在上面的例子中占位类型T就是一个类型参数.
// 类型参数可以在函数名后面加一个占位类型, 并用尖括号括起来.例如< T >
// 类型参数设定后,就可以当做函数的参数类型使用了,可以是参数类型也可以是返回类型
// 当函数被调用的时候,类型参数会被转化为实际类型
// 类型参数可以有多个,可以写在尖括号中,用逗号隔开
// 通常使用单个字母T U V来命名类型参数,但也可以是以大写字母开头的字符串


//:   泛型类型: 能够让自定义类,结构体和枚举适用于任何类型,类似于Array和Dictionary
struct Stack<Element> {                 // 模拟栈的操作过程的泛型集合类型
    var items = [Element]()             // 使用Element为空数组进行初始化
    mutating func push(item: Element) { // push的参数类型是Element
        items.append(item)
    }
    mutating func pop() -> Element {    // pop的返回值类型是Element类型
        return items.removeLast()
    }
}
var stackOfStrings = Stack<String>()  // 在尖括号中写出栈中需要存储的数据类型
stackOfStrings.push(item: "A")
stackOfStrings.push(item: "B")
stackOfStrings.push(item: "C")
let fromTheTop = stackOfStrings.pop()
print(stackOfStrings.items)             // ["A", "B"]


//:  泛型类型的扩展: 原类型中的类型参数在扩展中可以直接使用
extension Stack {
    var topItem: Element? {             // 返回栈顶元素的只读计算属性
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

if let topItem = stackOfStrings.topItem {
    print("topItem:\(topItem)")         // topItem:B
}


//:  类型约束:swapTwoValues(_:_:)和Stack适用于任何类型,但有时我们需要对类型进行一些约束,这些约束可以是类型参数必须继承指定类,或者符合特定的协议或协议组合
class SomeClass {}
protocol SomeProtocol {}
// T的类型参数必须是SomeClass的子类,U的类型参数必须遵守SomeProtocol协议
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // 类型约束语法
}

// 根据字符串查找在数组中的索引
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        // 为T增加一个Equatable协议,遵循该协议的类型必须实现等式符和不等符号.才可以对两个类型进行比较
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let doubleIndex = findIndex(of: 5.0, in: [3.2, 1.0, 5.0, 5,6]) // 2
let stringIndex = findIndex(of: "O", in: ["V", "D", "E", "O"]) // 3


//:  类型的关联: 关联类型为协议中某个类型提供别名
protocol Container {                     // 定义一个Container协议
    associatedtype ItemType             // 通过associatedtype关键词来定义一个关联类型
    mutating func append(item: ItemType) // 添加一个新的元素到容器里
    var count: Int { get }              // 获取容器中的元素个数
    subscript(i: Int) -> ItemType { get } // 通过索引获取容器中的元素
}

struct NewStack<Element>: Container {
    var items = [Element]()
    mutating func push(item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    mutating func append(item: Element) {
        self.push(item: item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {      // swift会自动推断Element就是ItemType类型
        return items[i]                 // 不需要再去指定ItemType类型为Element类型
    }
}


//:  where子句: 为泛型的类型参数做一些约束
// where语句指定C1和C2的类型必须一致,且C1遵循Equatable协议
func allItemMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer
    : C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
    // 检查两个容器是否含有相同的元素
    if someContainer.count != anotherContainer.count {
        return false
    }
    // 检查每一对元素是否相等
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}
var stackOfString = NewStack<String>()
stackOfString.push(item: "O")
stackOfString.push(item: "P")
stackOfString.push(item: "Q")

var arrayOfString = NewStack<String>()
arrayOfString.items = ["O", "P", "Q"]

if allItemMatch(stackOfString, arrayOfString) { // 判断两个遵循Container协议的数组元素是否一致
    print("全部元素匹配")                 // 全部元素匹配
}else {
    print("元素不全部匹配")
}


//:  访问控制: swift中可以对各种类型进行访问控制,从而隐藏代码的实现细节,为其他人访问代码提供接口
// 访问控制有两个基本单元,模块和源文件.模块:一般为单独的框架或者应用,使用模块需要用import导入
//                               源文件:应用或框架中一个个文件
// 访问级别: 公开访问: 可以在同一模块中访问任何类型,如果在导入模块后,别的模块也能访问该模块的任何类型
//          内部访问: 只能在模块内部访问,模块外部不能访问
//          文件私有访问: 只能在文件内部访问
//          私有访问: 只能在被限制的访问作用域来访问


//:  访问控制语法: 在类型前面加访问限制关键字public,internal,fileprivate,private
public class PublicClass {}             // 公开访问
internal class InternalClass {}         // 内部访问,如果不加访问限制那么默认添加internal
fileprivate class FileprivateClass {}   // 文件私有访问
private class PrivateClass {}           // 私有访问

// 元组类型的访问限制: 由元组中访问级别最严格的类型来决定
internal let x = 2
private let y = 3
let tupleA = (x, y)                     // 此时tupleA的访问级别为private

// 函数的访问限制是根据最严格的参数类型和返回类型的访问限制来决定的
// 函数前面不加private,就无法访问private的返回值.
private func someFunction() -> (InternalClass,PrivateClass){
    return (InternalClass(), PrivateClass())
}

// 枚举成员的访问级别和枚举类型相同,不能为枚举成员指定不同的类型
public enum CompassPoint {
    case North, South, East, West
}


//:  常量 变量 属性 下标不能拥有比它们更高的访问级别
private var privateC = PrivateClass()   // 如果类型是private级别的,变量的访问级别必须明确为private
// 通过设置set get方法为常量 变量 属性 下标 设置读写权限

struct ChangedString {                  // 记录value属性的修改次数
    private(set) var numberOfEdits = 0  // 该属性在该结构体中是可读可写的,但在该外面是只读的
    var value:String = "" {             // value为默认访问级别
        didSet {
            numberOfEdits += 1
        }
    }
}

var stringOfEdit = ChangedString()
stringOfEdit.value = "ABC"
stringOfEdit.value = "OPQ"
print(stringOfEdit.numberOfEdits)       // 2
//stringOfEdit.numberOfEdits = "XYZ"      // ❌,numberOfEdits属性的set方法的限制类型为private



//:  位运算符:操作数据结构中每个独立的比特位
// ~ : 按位取反运算符
let bits: UInt8 = 0b0011                // UInt8的范围为:0~255,bits表示十进制的3
print(bits)
let invertBits = ~bits                  // 252, 按位取反运算符(~),表示全部位取反

// & : 按位与运算符
let firstTwoBits = 0b10                 // 十进制的2
let secondTwoBits = 0b11                // 十进制的3
let fourBits = firstTwoBits & secondTwoBits // 十进制的2, 按位与结果为0b10

// | : 按位或运算符
let combinedBits = firstTwoBits | secondTwoBits // 十进制的2, 按位或结果为0b11

// ^ : 按位异或运算符
let outputBits = firstTwoBits ^ secondTwoBits // 十进制的1, 按位或结果为0b01

// << >> : 按位左移 右移运算符
let shiftBits: UInt = 0b00110           // 十进制的6
shiftBits << 1                          // 0b01100, 左移动一位产生一个新的值,十进制的12
shiftBits >> 1                          // 0b00011, 右移动一位产生一个新的值,十进制的3


//:  溢出运算符: 当一个数值超过了它的有效范围,swift会报错
var overflowInt: UInt8 = 255            // UInt8的范围为:0~255
//overflowInt = 256                       // ❌
//overflowInt = -1                        // ❌, 只要数不在范围内,不管值过大还是过小都会报错

// 数值的溢出直接报错太不友好了,其实可以做截断处理的
// 有以下几种方法可以处理: 溢出加法 &+ 溢出减法 &- 溢出乘法 &*
overflowInt = UInt8.max                 // 最大整数255
overflowInt = overflowInt &+ 1          // 0, 二进制加一的时候发生溢出就变成了00000000
overflowInt = UInt8.min
overflowInt = overflowInt &- 1          // 255, 当对其最小值进行减1,也会发生溢出变为11111111


//:  运算符函数: 可以通过运算符操作类和结构体
struct Point {
    var x = 0.0, y = 0.0
}
// 加法运算符
extension Point {
    // 该运算符函数需要接受两个Point类型参数,返回一个Point类型值
    static func + (left: Point, right: Point) -> Point {
        return Point(x: left.x + right.x, y: left.y + right.y)
    }
}

let point = Point(x: 2.0, y: 3.0)
let anotherPoint = Point(x: 1.0, y: 2.0)
print(point + anotherPoint)             // Point(x: 3.0, y: 5.0)

// 前缀和后缀运算符
extension Point {
    // 实现前缀或者后缀运算符需要在声明运算符的函数的时候在func之前加prefix和postfix关键字
    static prefix func - (point: Point) -> Point {
        return Point(x: -point.x, y: -point.y)
    }
}
let position = Point(x: 3.0, y: 4.0)
print(-position)                        // Point(x: -3.0, y: -4.0)

// 复合赋值运算符
extension Point {
    // 需要把左边的参数设置为inout,因为这个参数会在运算符内被修改
    static func += (left: inout Point, right: Point) {
        left = left + right
    }
}
var original = Point(x: 1.0, y: 3.0)
let pointToAdd = Point(x: 2.0, y: 3.0)
original += pointToAdd
print(original)                         // Point(x: 3.0, y: 6.0)


// 等价运算符
extension Point {
    // 通过函数来判断Point中的两个属性是否相等
    static func == (left: inout Point, right: Point) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
    // 通过调用相等运算符函数来判断两个Point不等
    static func != (left: inout Point, right: Point) -> Bool {
        return !(left == right)
    }
}
var twoThree = Point(x: 2.0, y: 3.0)
let anotherTwoThree = Point(x: 2.0, y: 3.0)
print(twoThree == anotherTwoThree)      // true
print(twoThree != anotherTwoThree)      // false


//:  自定义运算符: 除了使用系统的运算符,swift还支持自定义
// 使用operator关键字定义新的运算符,同时还要指定 prefix infix postfi
prefix operator +++                     // 需要先定义好双自增运算符
extension Point {
    static prefix func +++ (point: inout Point) -> Point {
        point += point                  // 调用 +=运算符函数
        return point
    }
}

var toBeDouble = Point(x: 2.0, y: 2.0)
+++toBeDouble
print(toBeDouble)                       // Point(x: 4.0, y: 4.0)

toBeDouble += toBeDouble
print(toBeDouble)

true || false

let someString = String()
//someString = "ljk"

var num1: Set = ["3", "2", "1", "5", "7"]
let num2: Set = ["3", "2", "1", "5", "7"]
let num3: Set = ["1", "3", "5"]

num1.intersection(num2)              // ["1", "3", "5"], 两个集合都包含的值的集合

num1.union(num2)                     // 两个集合的所有元素组成的集合

num1.subtracting(num2)               // ["2", "7"], 只在num1中有的元素组成的集合

num1.symmetricDifference(num2)       // 在一个集合中但不在两个集合中的元素组成的集合





